(function ($) {

	if (typeof $.esmool == 'undefined')
		$.esmool = {};

	var beginShow = () => {};
	var endShow = () => {};
	var showDogs = () => {};

	$.esmool.gaInit = (beginShowFunc, showDogsFunc, endShowFunc) => {
		beginShow = beginShowFunc;
		showDogs = showDogsFunc;
		endShow = endShowFunc;
	};

	/* {success, theOne, iterations} */ $.esmool.ga = (
			/* int */ population, /* int */ iterations,
			/* double */ xmin, /* double */ xmax,
			/* Function<Double, Double> */ target,
			/* Function<Double, Double> */ goodness,
			/* BiFunction<Double, Double, Double> */ babyMaker,
			/* Function<Double, Double> */ mutator,
			/* Function<double[], Boolean> */ successChecker,
			/* double */ killingRate) => {
		let theOne = null;
		let c = 0;

		beginShow();
		let dogs = initialize(population, xmin, xmax);
		showDogs(dogs, target, "initial", xmin, xmax);

		while (c < iterations) {
			dogs = killDogs(dogs, killingRate, goodness);
			theOne = choseTheOne(dogs, target, successChecker);
			if (c++ % 10 == 0) {
				let outFile = String.format("DATA %02d", c - 1);
				showDogs(dogs, target, outFile, xmin, xmax);
			}

			if (theOne != null)
				break;

			dogs = makeBabies(population, dogs, babyMaker);
			dogs = mutateDogs(dogs, mutator);
		}

		showDogs(dogs, target, "final", xmin, xmax);
		endShow();

		return {
			success: theOne != null,
			theOne: theOne,
			iterations: c
		};
	}

	function /* double */ choseTheOne(
			/* double[] */ dogs,
			/* Function<Double, Double> */ target,
			/* Function<double[], Boolean> */ successChecker) {
		return successChecker(dogs) ? dogs[0] : null;
	}

	function /* double[] */ mutateDogs(/* double[] */ dogs, /* Function<Double, Double> */ mutator) {
		for (let i = 0; i < dogs.length; i++)
			dogs[i] = mutator(dogs[i]);
		return dogs;
	}

	function /* double[] */ makeBabies(/* int */ population, /* double[] */ dogs, /* BiFunction<Double, Double, Double> */ babyMaker) {
		let offset = dogs.length;
		let toBirth = population - offset;
		let result = new Array(population);
		for (let i = 0; i < offset; i++)
			result[i] = dogs[i];

		let nextInt = max => Math.floor(max * Math.random());
		for (let i = 0; i < toBirth; i++) {
			let papa = nextInt(offset);
			let mama = nextInt(offset);
			let baby = babyMaker(dogs[papa], dogs[mama]);
			result[i + offset] = baby;
		}

		return result;
	}

	function /* double[] */ killDogs(/* double[] */ dogs, /* double */ killingRate, /* Function<Double, Double> */ goodness) {
		let population = dogs.length;
		let list = [];
		for (let i = 0; i < population; i++)
			list.push({ a: i, b: goodness(dogs[i]) });
		list.sort((t1, t2) => t1.b < t2.b ? 1 : t1.b == t2.b ? 0 : -1);

		let p = 0, len = list.length;
		let toSurvive = Math.floor(population * (1 - killingRate));
		let luckyOnes = new Array(toSurvive);
		for (let i = 0; p < len && i < toSurvive; i++) {
			let index = list[p++].a;
			luckyOnes[i] = dogs[index];
		}
		return luckyOnes;
	}

	function /* double[] */ initialize(/* int */ population, /* double */ xmin, /* double */ xmax) {
		let dx = (xmax - xmin) / (population - 1);
		let dogs = new Array(population);
		for (let i = 0; i < population; i++)
			dogs[i] = xmin + dx * i;
		return dogs;
	}

})(jQuery);